﻿
namespace Orchard.OData.Services {
    using Microsoft.OData.Core;
    using Microsoft.OData.Edm;
    using System;
    using System.Linq;
    using System.Net;
    using System.Web;

    public interface IODataHttpHandler : IHttpHandler, IDependency {
        void ProcessMetadataRequest(HttpContext context, IEdmModel edmModel);
        void ProcessServiceDocumentRequest(HttpContext context, IEdmModel edmModel);
        void ProcessDataRequest(HttpContext context, IEdmModel edmModel);
    }

    public sealed class ODataHttpHandler : IODataHttpHandler {
        internal const string odata_version = "4.0";

        private readonly IMetadataBuilder _metadataBuilder;
        private readonly IDataRequestBroker _dataRequestBroker;
        public ODataHttpHandler(IMetadataBuilder metadataBuilder, IDataRequestBroker dataRequestBroker) {
            this._metadataBuilder = metadataBuilder;
            this._dataRequestBroker = dataRequestBroker;
        }

        bool IHttpHandler.IsReusable { get { return false; } }

        void IHttpHandler.ProcessRequest(HttpContext context) {
            var oDataHttpHandler = this as IODataHttpHandler;
            var edmModel = this._metadataBuilder.BuildModel();

            var httpRequest = context.Request;
            var httpResponse = context.Response;

            httpResponse.Headers.Add(ODataConstants.ODataVersionHeader, ODataHttpHandler.odata_version);
            var lastUriSegment = httpRequest.Url.Segments.LastOrDefault();
            
            try {
                if (httpRequest.HttpMethod.ToLower() != "get") {
                    throw new ODataErrorException(new ODataError() { 
                        ErrorCode = HttpStatusCode.NotImplemented.ToString(), 
                        InnerError = new ODataInnerError(new NotImplementedException()) });
                }            
                if (lastUriSegment.ToLower().StartsWith("odata")) {
                    oDataHttpHandler.ProcessServiceDocumentRequest(context, edmModel);
                    return;
                }
                if (lastUriSegment.ToLower() == "$metadata") {
                    oDataHttpHandler.ProcessMetadataRequest(context, edmModel);
                    return;
                }
                oDataHttpHandler.ProcessDataRequest(context, edmModel);
            }
            catch (ODataErrorException oDataException) {
                HttpStatusCode httpStatusCode = HttpStatusCode.BadRequest;
                Enum.TryParse<HttpStatusCode>(oDataException.Error.ErrorCode, out httpStatusCode);
                var odataWriter = context.GetODataMessageWriter(httpStatusCode, MimeConstants.MimeApplicationJson, edmModel);
                odataWriter.WriteError(oDataException.Error, true);
            }
            catch {
                throw;
            }
        }

        void IODataHttpHandler.ProcessMetadataRequest(HttpContext context, IEdmModel edmModel) {
            var odataWriter = context.GetODataMessageWriter(HttpStatusCode.OK, MimeConstants.MimeApplicationXml, edmModel);
            odataWriter.WriteMetadataDocument();
        }

        void IODataHttpHandler.ProcessServiceDocumentRequest(HttpContext context, IEdmModel edmModel) {
            var odataWriter = context.GetODataMessageWriter(HttpStatusCode.OK, MimeConstants.MimeApplicationJson, edmModel);
            odataWriter.WriteServiceDocument(edmModel.GenerateServiceDocument());
        }

        void IODataHttpHandler.ProcessDataRequest(HttpContext context, IEdmModel edmModel) {
            var contentItems = this._dataRequestBroker.ExecuteQuery(context.Request, edmModel);
            
            if (!(null != contentItems && contentItems.Any())) {
                throw new ODataErrorException(new ODataError() { 
                    ErrorCode = HttpStatusCode.NoContent.ToString() });
            }

            var contentItemEntries = contentItems
                .Select(contentItem => this._dataRequestBroker.BuildEntry(contentItem, edmModel))
                .ToList();
            
            var entitySetName = contentItems.First().TypeDefinition.Name + "s";
            var entitySet = edmModel.EntityContainer.EntitySets().First(es => es.Name == entitySetName);
            var entityType = entitySet.EntityType();

            var odataWriter = context.GetODataMessageWriter(HttpStatusCode.OK, MimeConstants.MimeApplicationJson, edmModel);
            ODataWriter feedWriter = odataWriter.CreateODataFeedWriter(entitySet, entityType);
            feedWriter.WriteStart(new ODataFeed() {
                Id = new System.Uri(context.Request.ODataRootService(), entitySet.Name)
            });
            contentItemEntries.ForEach(cie => {
                feedWriter.WriteStart(cie);
                feedWriter.WriteEnd();
            });
            feedWriter.WriteEnd();
        }
    }

}